- 1. 错误处理
- 2. 生成器委托
- 3. 总结
译注1:此文带着自己的理解,不完全按原文翻译。原文地址
译注2:原文晦涩难懂的地方,尽力做了注释或修饰,方便大家理解。错误之处欢迎各位校验指正。
如果你仍然对 ES6 Generators 不熟悉的话,建议你先阅读并运行 【译】ES6 Generators 基础篇(1) 中的代码片段,理解了生成器的基础知识后,就可以阅读这篇文章了解更多的细节啦。
错误处理
ES6 中生成器的其中一个强大的特点就是:函数内部的代码编写风格是同步的,即使外部的迭代控制过程可能是异步的。
也就是说,我们可以简单地对错误进行处理,类似我们熟悉的 try..catch
语法,举个栗子:
1 2 3 4 5 6 7 8 9
| function *foo() { try { var x = yield 3; console.log( "x: " + x ); } catch (err) { console.log( "Error: " + err ); } }
|
即使这个生成器可能会在 yield 3
处中断,当接收到外部传入的错误时,try..catch
将会捕获到。
具体一个错误是怎样传入生成器的呢,举个栗子:
1 2 3 4 5 6
| var it = foo(); var res = it.next(); it.throw( "Oops!" );
|
我们可以使用 throw()
方法产生错误传进生成器中,那么在生成器中断的地方,即 yield 3
处会产生错误,然后被 try..catch
捕获。
注意:如果我们使用 throw()
方法产生一个错误传进生成器中,但没有对应的 try..catch
对错误进行捕获的话,这个错误将会被传出去,外部如果不对错误进行捕获的话,则会抛出异常:
1 2 3 4 5 6 7 8 9 10 11
| function *foo() { } var it = foo(); try { it.throw( "Oops!" ); } catch (err) { console.log( "Error: " + err ); }
|
当然,我们也可以进行反方向的错误捕获:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function *foo() { var x = yield 3; var y = x.toUpperCase(); yield y; } var it = foo(); it.next(); try { it.next( 42 ); } catch (err) { console.log( err ); }
|
生成器委托
另一个我们想做的可能是在一个生成器中调用另一个生成器。
我并不是指在一个生成器中初始化另一个生成器,而是说我们可以将一个生成器的迭代器控制交给另一个生成器。
为了实现委托,我们需要用到 yield
关键字的另一种形式:yield *
,举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function *foo() { yield 3; yield 4; } function *bar() { yield 1; yield 2; yield *foo(); yield 5; } for (var v of bar()) { console.log( v ); }
|
以上这段代码应该通俗易懂:当生成器 bar()
迭代到 yield 2
时,先将控制权交给了另一个生成器 foo()
迭代完后再将控制权收回,继续进行迭代。
这里使用了 for..of
循环进行示例,正如在基础篇我们知道 for..of
循环中没有暴露出 next()
方法来传递值到生成器中,所以我们可以用手动的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function *foo() { var z = yield 3; var w = yield 4; console.log( "z: " + z + ", w: " + w ); } function *bar() { var x = yield 1; var y = yield 2; yield *foo(); var v = yield 5; console.log( "x: " + x + ", y: " + y + ", v: " + v ); } var it = bar(); it.next(); it.next( "X" ); it.next( "Y" ); it.next( "Z" ); it.next( "W" ); it.next( "V" );
|
尽管我们在这里只展示了一层的委托关系,但具体场景中我们当然可以使用多层的嵌套。
一个 yield *
技巧是,我们可以从被委托的生成器(比如示例中的 foo()
) 获取到返回值,举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function *foo() { yield 2; yield 3; return "foo"; // 返回一个值给 `yield*` 表达式 } function *bar() { yield 1; var v = yield *foo(); console.log( "v: " + v ); yield 4; } var it = bar(); it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // "v: foo" { value:4, done:false } 注意:在这里获取到了返回的值 it.next(); // { value:undefined, done:true }
|
yield *foo()
得到了 bar()
的控制权,完成了自己的迭代操作后,返回了一个 v: foo
值 给bar()
,然后 bar()
再继续迭代下去。
yield
和 yield *
表达式的一个有趣的区别是:在 yield
中,返回值在 next()
中传入的,而在 yield *
中,返回值是在 return
中传入的。
此外,我们也可以在委托的生成器中进行双向的错误绑定,举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function *foo() { try { yield 2; } catch (err) { console.log( "foo caught: " + err ); } yield; throw "Oops!"; } function *bar() { yield 1; try { yield *foo(); } catch (err) { console.log( "bar caught: " + err ); } } var it = bar(); it.next(); it.next(); it.throw( "Uh oh!" ); it.next();
|
throw( "Uh oh!" )
在代理给 foo()
的过程中,抛了个错误进去,所以错误在 foo()
中被捕获。
同理,throw "Oops!"
在 foo()
内部抛出的错误,将会传回给 bar()
后,被 bar()
中的 try..catch
捕获到。
总结
生成器有着同步方式的编写语法,意味着我么可以使用 try..catch
在 yield
表达式中进行错误处理。
生成器迭代器中也有一个 throw()
方法用于在中断期间向生成器内部传入一个错误,这个错误能被生成器内部的 try..catch
捕获。
yield *
允许我们将迭代器的控制权从当前的生成器中委托给另一个生成器。好处是 yield *
扮演了在生成器间传递消息和错误的角色。
了解了这么多,还有一个很重要的问题没有解决:
怎么异步地使用生成器呢?
关键是要实现这么一个机制:在异步环境中,当迭代器的 next()
方法被调用,我们需要定位到生成器中断的地方重新启动。
别担心,请听下回分解:)